
#include <lua.h>
#include <lauxlib.h>
#include <string.h>
#include <stdbool.h>

#include "vxi11.h"
#include "vxi11_svc.h"
#include "scpi_lexer.h"

#include "share/utils.h"
#include "share/lua_utils.h"

#include "share/log.h"
#define LOGLEVEL LOG_DEBUG
#define LOGTAG   "VXI11-LUA"
#include "share/log4app.h"

static struct {
	std_err_t std;
	Device_ErrorCode vxi;
} _vxi11_stderr_map[] = {
	{STD_ERR_OK					, Device_ErrorCode_OK                           },
	{STD_ERR_SYNTAX				, Device_ErrorCode_Syntax_error                 },
	{STD_ERR_NOT_ACCESSIBLE		, Device_ErrorCode_Device_not_accessible        },
	{STD_ERR_INVALID_ID			, Device_ErrorCode_Invalid_link_identifier      },
	{STD_ERR_INVALID_ARG		, Device_ErrorCode_Parameter_error              },
	{STD_ERR_DISCONNECTED		, Device_ErrorCode_Channel_not_established      },
	{STD_ERR_NOT_SUPPORTED		, Device_ErrorCode_Operation_not_supported      },
	{STD_ERR_NO_RESOURCES		, Device_ErrorCode_Out_of_resources             },
	{STD_ERR_LOCKED				, Device_ErrorCode_Device_locked_by_another_link},
	{STD_ERR_NO_LOCK			, Device_ErrorCode_No_lock_held_by_this_link    },
	{STD_ERR_TIMEOUT			, Device_ErrorCode_IO_timeout                   },
	{STD_ERR_IO					, Device_ErrorCode_IO_error                     },
	{STD_ERR_INVALID_ADDR		, Device_ErrorCode_Invalid_address              },
	{STD_ERR_ABORT				, Device_ErrorCode_Abort                        },
	{STD_ERR_OCCUPIED			, Device_ErrorCode_Channel_already_established  },
};

Device_ErrorCode vxi11_error_from_stderr(std_err_t err) {
	for(size_t i=0; i < COUNT(_vxi11_stderr_map); i++) {
		if(err == _vxi11_stderr_map[i].std) return _vxi11_stderr_map[i].vxi;
	}
	return (Device_ErrorCode)err;
}

std_err_t vxi11_error_to_stderr(Device_ErrorCode code) {
	for(size_t i=0; i < COUNT(_vxi11_stderr_map); i++) {
		if(code == _vxi11_stderr_map[i].vxi) return _vxi11_stderr_map[i].std;
	}
	return (std_err_t)code;
}

// @see scpi_lua.c
extern void lua_push_scpi_cmdgroup(lua_State* L, const scpi_cmd_group_t* group);

static Device_ErrorCode _vxi11_server_scpi_cmd_handler(void *cookie, instrument_t *inst, const char *data, size_t datalen) {
	if(!cookie || !inst) return Device_ErrorCode_Channel_not_established;
	Device_ErrorCode vxierr = Device_ErrorCode_OK;

	lua_State *L = (lua_State*)cookie;
	void* yybuf = NULL;
	int ret = 0;

	do {
		// 准备解析数据
		scpi_cmd_group_t* cmdgroup = NULL;
		std_err_t err = scpi_parse_prepare(data, datalen, &yybuf);
		if (err) { vxierr = vxi11_error_from_stderr(err); break; }

		while(1) {
			// !!! 启动解析 !!!
			err = scpi_parse_next(&cmdgroup);
			if(err) break;
			if(cmdgroup->cmd_count == 0) break; // 没有更多命令了，处理结束

			// !!! 调用 scpi.execute() 执行解析出来的 SCPI 命令组 !!!
			// 在指定的 instrument 上执行指定的 SCPI 命令组
			// ok, err = scpi.execute(pkg, instid, cmdgroup)
			// @param pkg 指令包
			// @param instid 指定的 instrument id
			// @param cmdgroup 要执行的 SCPI 命令组
			// @return 无返回值。遇到错误时抛出 error.
			                                     //luaL_printtop(L); // =1: pkg
			lua_getglobal(L, "scpi");            //luaL_printtop(L); // +1: scpi
			lua_getfield(L, -1, "execute");      //luaL_printtop(L); // +1: scpi.execute
			lua_pushvalue(L, 1);                 //luaL_printtop(L); // +1: pkg
			lua_pushinteger(L, inst->id);        //luaL_printtop(L); // +1: instid
			lua_push_scpi_cmdgroup(L, cmdgroup); //luaL_printtop(L); // +1: cmdgroup
			//lua_call(L, 3, 0);                 //luaL_printtop(L); // =2: pkg, scpi
			ret = lua_pcall(L, 3, 0, 0);         //luaL_printtop(L); // =2 or 3: pkg, scpi, (errmsg)
			if(ret) {
				size_t errmsglen = 0;
				const char *errmsg = luaL_checklstring(L, 3, &errmsglen);
				loge("SCPI execute failed (%d): %s\r\n", ret, errmsglen ? errmsg : "(unknown)");
				// SCPI 指令组执行失败，不再继续解析剩下的 SCPI 指令？
				break;
			}
		}
	} while(0);

	// 只留下 pkg 在栈上，后面还要用
	lua_pop(L, lua_gettop(L) - 1);       //luaL_printtop(L); // = 1: pkg

	// 解析结束，释放资源
	scpi_parse_finish(yybuf);
	return Device_ErrorCode_OK;
}

/**
 * 用于处理命令返回值的回调函数: function(cookie, instid, ok, resp)
 * @param cookie [any] 暂未使用
 * @param instid [int] 是在哪个 instrument 上执行的命令
 * @param ok [bool] SCPI 指令执行是否成功，true-成功; false-出现异常
 * @param resp [string] ok==true 时表示 SCPI 指令返回值; ok==true 时表示错误信息
 * @note 此函数是在 scpienv.lua: scpi.execute() 中直接调用的，有自己独立的调用栈，不会影响其它函数的调用栈
 */
static int _vxi11_server_resp_handler(lua_State* L) {
	//int cookie = luaL_checkint(L, 1); // 暂未使用
	int instid = luaL_checkint(L, 2);
	bool ok = luaL_checkboolean(L, 3);
    size_t resplen = 0;
    const char *resp = lua_tolstring(L, 4, &resplen);

	instrument_t *inst = vxi11_server_get_instrument(instid);
	if(!inst) { luaL_error(L, "No such instrument: %d", instid); return 0; }
	if(!ok) loge("inst%d: SCPI resp error: %s", instid, resp);

    // 检查缓冲区空间大小
    if(inst->resplen + resplen + 2 > sizeof(inst->resp)) {
        loge("inst%u response buffer overflow !", inst->id);
	    return 0;
    }

    // 如果当前缓冲区中已经有数据，则需要在当前数据后面添加一个分号，以表示与上一条命令返回值的区隔
    if(inst->resplen) inst->resp[inst->resplen++] = ';';
    // 把数据添加到响应数据缓冲区后面
    memcpy(inst->resp + inst->resplen, resp, resplen); inst->resplen += resplen;
    // 添加字符串结束符 '\0'
    inst->resp[inst->resplen] = '\0';
	return 0;
}

/**
 * 退出 VXI11 Server
 * @param pkg [table] (未用) SCPI 指令包
 */
static int _vxi11_server_exit(lua_State* L) {
    vxi11_server_exit();
    return 0;
}

/**
 * 在当前线程中运行 VXI11 Server，直到调用 vxi11_server_exit()
 * ok, err = lvxi11.server_run(pkg, tcp_enable, udp_enable)
 * @param pkg [table] (必需) SCPI 指令包
 * @param tcp_enable [bool] (可选)是否启用 TCP 协议。默认为 true。
 * @param udp_enable [bool] (可选)是否启用 UDP 协议。默认为 false。
 * @return 无。失败时抛出 error。
 */
static int _vxi11_server_run(lua_State* L) {
	luaL_checktype(L, 1, LUA_TTABLE);
	bool tcp_enable = luaL_optboolean(L, 2, true);
	bool udp_enable = luaL_optboolean(L, 3, false);

	// !!! 只留下 pkg 参数在栈上，在 _vxi11_scpi_cmd_handler() 回调函数中会使用此参数 !!!
	lua_pop(L, lua_gettop(L) - 1);
	
	// 提取并检查 pkg.instrument_count
	long inst_count = luaL_getfield_long(L, 1, "instrument_count", -1);
	if(inst_count < 1) { luaL_error(L, "Invalid instrument count: %d", inst_count); return 0; }

	// pkg._resp_handler = lvxi11.resp_handler, 此函数会在 scpi.execute() 处理查询命令时回调
	lua_pushcfunction(L, _vxi11_server_resp_handler); lua_setfield(L, 1, "_resp_handler");
	// pkg._resp_handler_cookie 暂未使用，无需设置
	//lua_pushnumber(L, 12345678); lua_setfield(L, 1, "_resp_handler_cookie");
	
	// pkg.server_exit = lvxi11.server_exit, 可在 SCPI 函数中调用它退出主循环，关闭服务
	lua_pushcfunction(L, _vxi11_server_exit); lua_setfield(L, 1, "server_exit");

	// !!! never return until vxi11_server_exit() is called !!!
    std_err_t err = vxi11_server_run(inst_count, tcp_enable, udp_enable, _vxi11_server_scpi_cmd_handler, L);
	if(err) { luaL_error(L, "Failed to start VXI11 server: %s", std_err_comment(err)); return 0; }
	return 0;
}


typedef struct {
	CLIENT *core;
	CLIENT *async;
	CLIENT *intr;
	long clientid;
	Device_Link lid;
} vxi11_client_t;

/**
 * lvxi11.client_close(client)
 */
static int _vxi11_client_close(lua_State* L) {
	vxi11_client_t *client = lua_touserdata(L, 1);
	if(client) {
		if(client->core ) clnt_destroy(client->core);
		if(client->async) clnt_destroy(client->async);
		if(client->intr ) clnt_destroy(client->intr);
		free(client);
	}
	return 0;
}

/**
 * client = lvxi11.client_connect(host, instname, proto, clientid, lock, lock_timeout, chasync, chintr)
 */
static int _vxi11_client_connect(lua_State* L) {
	const char *host    = luaL_optstring(L, 1, "localhost");
	const char *inst    = luaL_optstring(L, 2, "inst0");
	const char *proto   = luaL_optstring(L, 3, "tcp");
	u_long clientid     = (u_long)luaL_optlong(L, 4, 1234);
	bool_t lock         = (bool_t)luaL_optboolean(L, 5, false);
	u_long lock_timeout = (u_long)luaL_optlong(L, 6, 3000);
	bool_t chasync      = (bool_t)luaL_optboolean(L, 7, false);
	bool_t chintr       = (bool_t)luaL_optboolean(L, 8, false);
	lua_pop(L, lua_gettop(L)); // 清除栈中所有数据

	// 分配 client 实例内存
	vxi11_client_t *client = MALLOC0(vxi11_client_t);
	if(!client) { return luaL_error(L, "MALLOC0(vxi11_client_t) failed !"); }	
	lua_pushlightuserdata(L, client); // 压入 client 实例指针 @1

	do {
		client->core  = clnt_create(host, DEVICE_CORE, DEVICE_CORE_VERSION, proto);
		if(client->core  == NULL) { _vxi11_client_close(L); return luaL_error(L, "clnt_create(CORE) failed !"); }

		if(chasync) {
			client->async = clnt_create(host, DEVICE_ASYNC, DEVICE_ASYNC_VERSION, proto);
			if(client->async == NULL) { _vxi11_client_close(L); return luaL_error(L, "clnt_create(ASYNC) failed !"); }
		}
		
		if(chintr) {
			client->intr  = clnt_create(host, DEVICE_INTR, DEVICE_INTR_VERSION, proto);
			if(client->intr  == NULL) { _vxi11_client_close(L); return luaL_error(L, "clnt_create(INTR) failed !"); }
		}
				
		Create_LinkParms create_link_1_arg;
		create_link_1_arg.clientId = clientid;
		create_link_1_arg.device = (char*)inst;
		create_link_1_arg.lockDevice = lock;
		create_link_1_arg.lock_timeout = lock_timeout;
		Create_LinkResp *create_link_1_result = create_link_1(&create_link_1_arg, client->core);
		if (create_link_1_result == (Create_LinkResp *) NULL || create_link_1_result->error) { _vxi11_client_close(L); return luaL_error(L, "create_link() failed !"); }
		client->lid = create_link_1_result->lid;
		client->clientid = clientid;
		logd("link created: client%d == link%d == %s\n", client->clientid, client->lid, inst);
	} while(0);

	return 1;
}

/**
 * write_size = lvxi11.client_core_write(client, data, timeout, flags)
 */
static int _vxi11_client_core_write(lua_State* L) {
	vxi11_client_t *client = lua_touserdata(L, 1);       if(!client || !client->core) return luaL_error(L, "Invalid client or client->core !");
	size_t dlen = 0;
	const char *data   = luaL_checklstring(L, 2, &dlen); if(!data || !dlen) return luaL_error(L, "Data is zero-length !");
	u_long timeout     = (u_long)luaL_optlong(L, 3, 1000);
	Device_Flags flags = (Device_Flags)luaL_optlong(L, 4, 0);

	Device_WriteParms  device_write_1_arg;
	device_write_1_arg.lid = client->lid;
	device_write_1_arg.flags = flags;
	device_write_1_arg.io_timeout = timeout;
	device_write_1_arg.lock_timeout = timeout;
	device_write_1_arg.data.data_len = dlen;
	device_write_1_arg.data.data_val = (char*)data;
	Device_WriteResp *device_write_1_result = device_write_1(&device_write_1_arg, client->core);
	if(device_write_1_result == (Device_WriteResp*) NULL) return luaL_error(L, "_vxi11_client_core_write() failed !");
	if(device_write_1_result->error) return luaL_error(L, std_err_comment(vxi11_error_to_stderr(device_write_1_result->error)));
	
	lua_pushinteger(L, (lua_Integer)device_write_1_result->size);
	return 1;
}

/**
 * data = lvxi11.client_core_read(client, request_size, timeout, flags, termchar)
 */
static int _vxi11_client_core_read(lua_State* L) {
	vxi11_client_t *client = lua_touserdata(L, 1);      if(!client || !client->core) return luaL_error(L, "Invalid client or client->core !");
	u_long request_size = (u_long)luaL_checklong(L, 2); if(!request_size) return luaL_error(L, "Zero read size !");
	u_long timeout      = (u_long)luaL_optlong(L, 3, 1000);
	Device_Flags flags  = (Device_Flags)luaL_optlong(L, 4, 0);
	char termchar       = (char)luaL_optint(L, 5, '\0');
	
	Device_ReadParms device_read_1_arg;
	device_read_1_arg.lid = client->lid;
	device_read_1_arg.flags = flags;
	device_read_1_arg.io_timeout = timeout;
	device_read_1_arg.lock_timeout = timeout;
	device_read_1_arg.requestSize = request_size;
	device_read_1_arg.termChar = termchar;
	Device_ReadResp *result = device_read_1(&device_read_1_arg, client->core);
	if (result == (Device_ReadResp *) NULL) return luaL_error(L, "_vxi11_client_core_read() failed !");
	
	lua_pushlstring(L, result->data.data_val, result->data.data_len);
	return 1;
}

/**
 * errmsg = lvxi11.client_async_abort(client)
 */
static int _vxi11_client_async_abort(lua_State* L) {
	vxi11_client_t *client = lua_touserdata(L, 1); if(!client || !client->async) return luaL_error(L, "Invalid client or client->async !");
	Device_Link device_abort_1_arg;
	Device_Error *result = device_abort_1(&device_abort_1_arg, client->async);
	if(result == (Device_Error *) NULL) return luaL_error(L, "_vxi11_client_core_read() failed !");
	if(result->error != Device_ErrorCode_OK) {
		lua_pushstring(L, std_err_comment(vxi11_error_to_stderr(result->error)));
		return 1;
	}
	return 0;
}

/**
 * lvxi11.client_intr_srq(client)
 */
static int _vxi11_client_intr_srq(lua_State* L) {
	vxi11_client_t *client = lua_touserdata(L, 1); if(!client || !client->intr) return luaL_error(L, "Invalid client or client->intr !");
	Device_SrqParms device_intr_srq_1_arg;
	void *result = device_intr_srq_1(&device_intr_srq_1_arg, client->intr);
	if (result == (void *) NULL) return luaL_error(L, "_vxi11_client_intr_srq() failed !");
	return 0;
}


#ifdef LVXI11DLL

// 仅在独立编译为一个 DLL 时才需要定义自己的日志
logger_t *_logger = NULL;

static int _logger_attach(lua_State* L) {
    _logger = lua_touserdata(L, 1);
    return 0;
}

#endif

static const struct luaL_Reg _lib[] = {
    //{"resp_handler", _vxi11_resp_handler},
    //{"server_exit", _vxi11_server_exit},
    {"server_run", _vxi11_server_run},
    {"client_close", _vxi11_client_close},
    {"client_connect", _vxi11_client_connect},
    {"client_core_write", _vxi11_client_core_write},
    {"client_core_read", _vxi11_client_core_read},
    {"client_async_abort", _vxi11_client_async_abort},
    {"client_intr_srq", _vxi11_client_intr_srq},
#ifdef LVXI11DLL
	{"_logger_attach", _logger_attach},
#endif
	{NULL, NULL},
};

static struct {
	char* name;
	int32_t value;
} _consts[] = {
	{NULL, 0}
};

extern int luaopen_scpi(lua_State* L);

int luaopen_lvxi11(lua_State* L) {
	// 先加载其它依赖库
	luaopen_scpi(L); lua_pop(L, 1);

    #if LUA_VERSION_NUM < 502
        luaL_register(L, "lvxi11", _lib);
    #else
        luaL_newlib(L, _lib);
    #endif
	for (int i = 0; _consts[i].name != NULL; i++) {
		lua_pushstring(L, _consts[i].name);
		lua_pushnumber(L, _consts[i].value);
		lua_settable(L, -3);
	}

	return 1;
}
